#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/input.h>
#include <linux/interrupt.h>

#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/timer.h>
#include <linux/delay.h>

#include <linux/pinctrl/consumer.h>
#include <linux/err.h>
#include <linux/input/mt.h>

#define DRIVER_NAME "gpio-in"
#define MAX_DEVICES_IRQ_NAME 20
#define SUCESS 1
#define FAILED 0

struct class *gpio_in_class;
static dev_t gpio_in_devno;
static int major;
static struct input_dev *gpio_in_input;

struct gpio_in_dev {
	struct device 	*dev;
	struct cdev 	gpin_cdev;
	const char 	*gpio_in_dev_name;
	int dev_create_flag;
	int dev_code_flag;
	int gpio_num;
	int gpio_in_irq;
	int gpio_in_input_flag;
	int gpio_input_code;

	char gpio_in_irq_name[MAX_DEVICES_IRQ_NAME];
};

struct gpio_in_dev_priv {
	int num_gpins;
	struct gpio_in_dev gpins[0];
};

struct gpio_in_dev_priv * gpio_in_priv;

static const struct of_device_id of_gpio_in_match[] = {
	{
		.compatible = DRIVER_NAME,
	},
	{ /* sentinel */},
};

static ssize_t level_value_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct gpio_in_dev *gpin_dev = (struct gpio_in_dev*)dev_get_drvdata(dev);

	return sprintf(buf, "%u\n", gpio_get_value(gpin_dev->gpio_num));
}
static DEVICE_ATTR_RO(level_value);

static struct attribute *level_value_class_attrs[] = {
	&dev_attr_level_value.attr,
	NULL,
};

static const struct attribute_group level_value_group = {
	.attrs = level_value_class_attrs,
};

static const struct attribute_group *level_value_groups[] = {
	&level_value_group,
	NULL,
};

irqreturn_t gpio_in_irq_handler(int irq, void *dev_id)
{
	struct gpio_in_dev *gpins_pri = (struct gpio_in_dev *)dev_id;

	if (gpins_pri->gpio_in_input_flag) {
		gpins_pri->gpio_in_input_flag = 0;
		input_report_key(gpio_in_input, gpins_pri->gpio_input_code , gpins_pri->gpio_in_input_flag);
		input_sync(gpio_in_input);
	}
	else {
		gpins_pri->gpio_in_input_flag = 1;
		input_report_key(gpio_in_input, gpins_pri->gpio_input_code, gpins_pri->gpio_in_input_flag);
		input_sync(gpio_in_input);
	}

	return IRQ_HANDLED;
}

static inline size_t sizeof_gpio_in_dev_priv(int num_wiegands)
{
	return sizeof(struct gpio_in_dev_priv) + (sizeof(struct gpio_in_dev) * num_wiegands);
}

static int gpio_in_create_of(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node, *child;
	int count = 0;
	int ret = 0;
	count = of_get_child_count(np);
	if (count < 0)
		return -1;

	gpio_in_priv = devm_kzalloc(&pdev->dev, sizeof_gpio_in_dev_priv(count), GFP_KERNEL);

	for_each_child_of_node(np, child)
		if (of_get_gpio(child, 0) == -EPROBE_DEFER)
			return -1;

	for_each_child_of_node(np, child) {
		struct gpio_in_dev *gpio_in_dat = &gpio_in_priv->gpins[gpio_in_priv->num_gpins];

		gpio_in_dat->dev_create_flag = FAILED;
		gpio_in_dat->dev_code_flag = SUCESS;
		gpio_in_dat->gpio_num = of_get_named_gpio(child, "gpios", 0);
		if (gpio_in_dat->gpio_num < 0) {
			dev_info(&pdev->dev, "%s property not found %d\n",
						"gpio_num", __LINE__);
			of_node_put(child);
			continue;
		}

		gpio_in_dat->gpio_in_dev_name = of_get_property(child, "label", NULL) ? : child->name;

		dev_info(&pdev->dev, " %s:gpio_num: %d\n",
			gpio_in_dat->gpio_in_dev_name,gpio_in_dat->gpio_num);

		ret = gpio_request(gpio_in_dat->gpio_num, gpio_in_dat->gpio_in_dev_name);
		 if (ret < 0) {
			dev_err(&pdev->dev, "%s:request %d fail %d\n",
					gpio_in_dat->gpio_in_dev_name, gpio_in_dat->gpio_num, __LINE__);
			of_node_put(child);
			continue;
		}

		ret = gpio_direction_input(gpio_in_dat->gpio_num);
		if (ret < 0) {
			dev_err(&pdev->dev, "%s set in failed %d\n",
					gpio_in_dat->gpio_in_dev_name, __LINE__);
			of_node_put(child);
			continue;
		}

		ret = of_property_read_u32(child, "linux,code",
						&gpio_in_dat->gpio_input_code);
		if (ret < 0) {
			pr_err("%s[%d]: %s:get linux,conde failed\n",
					__func__, __LINE__, gpio_in_dat->gpio_in_dev_name);
			gpio_in_dat->dev_code_flag = FAILED;
		}

		gpio_in_dat->gpio_in_input_flag = gpio_get_value(gpio_in_dat->gpio_num);

		gpio_in_dat->dev = device_create(gpio_in_class, NULL, MKDEV(major, gpio_in_priv->num_gpins),
								gpio_in_dat, "%s", gpio_in_dat->gpio_in_dev_name);
		if (IS_ERR(gpio_in_dat->dev)) {
			pr_err("ERR:cannot create a wiegand_data\n");
			kfree(gpio_in_dat);
			of_node_put(child);
			continue;
		}
		gpio_in_dat->dev_create_flag = SUCESS;
		gpio_in_priv->num_gpins++;
	}
	return count;
}

static int gpio_in_probe(struct platform_device *pdev)
{
	int ret = 0, devices_num = 0,i;

	gpio_in_class = class_create(THIS_MODULE, DRIVER_NAME);
	if (IS_ERR(gpio_in_class))
		return PTR_ERR(gpio_in_class);
	gpio_in_class->dev_groups = level_value_groups;

	alloc_chrdev_region(&gpio_in_devno, 0, 255, DRIVER_NAME);
	major = MAJOR(gpio_in_devno);

	/* Analyzing Lebel form  DTS*/
	devices_num = gpio_in_create_of(pdev);
	if (devices_num < 0) {
		pr_err("Node registered failed!!! the ret is %d \n",devices_num);
		return -1;
	}

	/* input key regist*/
	gpio_in_input = input_allocate_device();
	if (!gpio_in_input)
		return -ENOMEM;
	gpio_in_input->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY);

	for (i = 0; i < devices_num; i++) {
		if (gpio_in_priv->gpins[i].dev_create_flag && gpio_in_priv->gpins[i].dev_code_flag) {
			gpio_in_input->keybit[BIT_WORD(gpio_in_priv->gpins[i].gpio_input_code)] |= BIT_MASK(gpio_in_priv->gpins[i].gpio_input_code);

			dev_info(&pdev->dev, "******%s, code=0x%02x**********\n",
							gpio_in_priv->gpins[i].gpio_in_dev_name, gpio_in_priv->gpins[i].gpio_input_code);

			gpio_in_priv->gpins[i].gpio_in_irq = gpio_to_irq(gpio_in_priv->gpins[i].gpio_num);

			ret = request_irq(gpio_in_priv->gpins[i].gpio_in_irq, gpio_in_irq_handler,
							IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
							gpio_in_priv->gpins[i].gpio_in_dev_name, &gpio_in_priv->gpins[i]);
			if (unlikely(ret)) {
				dev_err(&pdev->dev, "request  %s failed\n",
						gpio_in_priv->gpins[i].gpio_in_dev_name);
			}
		}
	}

	gpio_in_input->name = "gpio-in";
	gpio_in_input->phys = "gpio-in-key/input0";
	ret = input_register_device(gpio_in_input);
	if (ret) {
		pr_err("%s:%d: input_register faild\n",__func__, __LINE__);
		input_free_device(gpio_in_input);
		return ret;
	}

	return 0;
}

static struct platform_driver gpio_in_driver = {
	.probe		= gpio_in_probe,
	.driver		= {
	        .name	= DRIVER_NAME,
	        .owner	= THIS_MODULE,
	        .of_match_table = of_match_ptr(of_gpio_in_match),
	}
};

static int __init gpio_in_init(void)
{
	int ret = 0;
	ret = platform_driver_register(&gpio_in_driver);
	return ret;
}

static void gpio_in_exit(void)
{
	class_destroy(gpio_in_class);
	kfree(gpio_in_class);
	platform_driver_unregister(&gpio_in_driver);
	input_unregister_device(gpio_in_input);
}

module_init(gpio_in_init);
module_exit(gpio_in_exit);
MODULE_AUTHOR("lomboswer@lombotech.com");
MODULE_DESCRIPTION("LomboTech N7 gpio—in Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:gpio-in");
